package org.jpropelleralt.multiverse;

import org.jpropelleralt.box.Box;
import org.jpropelleralt.change.Change;
import org.jpropelleralt.reaction.Reaction;
import org.jpropelleralt.universe.Universe;
import org.jpropelleralt.universe.impl.UniverseDefault;
import org.jpropelleralt.view.View;

/**
 * The multiverse - provides actions that span all {@link Universe}s
 * and all {@link Box}es
 */
public class Multiverse {

	private final static Universe DEFAULT_UNIVERSE = new UniverseDefault();
	
	/**
	 * Each thread starts in the {@link #DEFAULT_UNIVERSE}
	 */
	private final static ThreadLocal<Universe> currentUniverse = new ThreadLocal<Universe>() {
		protected Universe initialValue() {
			return DEFAULT_UNIVERSE;
		};
	};
	
	/**
	 * Get the current {@link Universe} for the current {@link Thread}.
	 * This can be used as a simple way to control where {@link Box}es are
	 * created - just call {@link #setCurrentUniverse(Universe)} before
	 * creating {@link Box}es. Hence even if the {@link Box}es are nested
	 * (and one creates another, etc.) they will all be in the same {@link Universe}.
	 * 
	 * The "polite" way to use this is as:
	 * 	Universe oldUniverse = Multiverse.getCurrentUniverse();
	 * 	try {
	 * 		Multiverse.setCurrentUniverse(newUniverse);
	 * 		//Make stuff here
	 * 	} finally {
	 * 		Multiverse.setCurrentUniverse(oldUniverse);
	 * 	}
	 * 
	 * @return	The current {@link Universe} for this {@link Thread}
	 */
	public final static Universe getCurrentUniverse() {
		return currentUniverse.get();
	}
	
	/**
	 * Set the current {@link Universe} for this {@link Thread}.
	 * See {@link #getCurrentUniverse()} for a discussion of how to use this.
	 * @param newUniverse	The new current {@link Universe} for 
	 * 						this {@link Thread}
	 */
	public final static void setCurrentUniverse(Universe newUniverse) {
		if (newUniverse == null) throw new IllegalArgumentException("Can't set null current Universe");
		currentUniverse.set(newUniverse);
	}
	
	/**
	 * Called before a {@link Box} is read.
	 * Must call {@link #concludeRead(BoxSecrets)} when finished,
	 * preferably at the start of a try...finally 
	 * @param box 		{@link Box} about to be read
	 */
	public final static void prepareRead(Box box) {
		box.features().universe().internal().prepareRead(box);
	}

	/**
	 * Called after a {@link Box} is read.
	 * Must follow a call to {@link #prepareRead(Box)},
	 * preferably in the finally block of a try...finally
	 * @param box 		{@link Box} that has been read
	 */
	public final static void concludeRead(Box box){
		box.features().universe().internal().concludeRead(box);
	}
	
	/**
	 * Called before a {@link Box} is written to.
	 * Must call {@link #concludeWrite(Box)} when finished,
	 * preferably at the start of a try...finally
	 * @param box 		{@link Box} about to be written
	 */
	public final static void prepareWrite(Box box) {
		box.features().universe().internal().prepareWrite(box);
	}
	
	/**
	 * Called after each actual change to a {@link Box}.
	 * Must follow a call to {@link #prepareRead(Box)},
	 * preferably within the try block of a a try...finally
	 * @param box 		{@link Box} that has changed
	 * @param change	The change that just has been made
	 */
	public final static void propagateWrite(Box box, Change change){
		box.features().universe().internal().propagateWrite(box, change);
	}

	/**
	 * Called after a {@link Box} is written.
	 * Must follow a call to {@link #prepareWrite(Box)},
	 * preferably as a try...finally
	 * @param box 		{@link Box} that has written
	 */
	public final static void concludeWrite(Box box) {
		box.features().universe().internal().concludeWrite(box);
	}
	
	/**
	 * Called before a {@link Box} has {@link View} list changed.
	 * Must call {@link #concludeViewChange(Box)} when finished,
	 * preferably at the start of a try...finally 
	 * @param box 		{@link Box} about to have {@link View} list changed.
	 */
	public final static void prepareViewChange(Box box){
		box.features().universe().internal().prepareViewChange(box);
	}

	/**
	 * Called after a {@link Box} has {@link View} list changed.
	 * Must follow a call to {@link #prepareViewChange(Box)},
	 * preferably in the finally block of a try...finally
	 * @param box 		{@link Box} that has had {@link View} list changed.
	 */
	public final static void concludeViewChange(Box box){
		box.features().universe().internal().concludeViewChange(box);
	}

	/**
	 * Called before a {@link Box} has {@link Reaction} list changed.
	 * Must call {@link #concludeReactionChange(Box)} when finished,
	 * preferably at the start of a try...finally 
	 * @param box 		{@link Box} about to have {@link Reaction} list changed.
	 */
	public final static void prepareReactionChange(Box box){
		box.features().universe().internal().prepareReactionChange(box);
	}

	/**
	 * Called after a {@link Box} has {@link Reaction} list changed.
	 * Must follow a call to {@link #prepareReactionChange(Box)},
	 * preferably in the finally block of a try...finally
	 * @param box 		{@link Box} that has had {@link Reaction} list changed.
	 */
	public final static void concludeReactionChange(Box box){
		box.features().universe().internal().concludeReactionChange(box);
	}
}
